/*
 * Copyright (c) 2009, Takashi Ito
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "tssp_internal.h"

void tssp_section_destroy(struct tssp_section *section)
{
    struct tssp_section *tmp;

    if (section) {
        do {
            if (section->impl.destroy)
                section->impl.destroy(section);
            tmp = section->next_section;
            free(section);
            section = tmp;
        } while (section);
    }
}

struct tssp_descriptor *
tssp_descriptor_create(uint8_t tag, uint8_t length, const uint8_t *data)
{
    struct tssp_descriptor *desc;

    desc = malloc(sizeof(*desc));
    if (desc == NULL)
        return NULL;

    desc->data = malloc(length);
    if (desc->data == NULL) {
        free(desc);
        return NULL;
    }

    desc->tag = tag;
    desc->length = length;
    memcpy(desc->data, data, length);
    desc->decoded = NULL;
    desc->destroy = NULL;

    return desc;
}

void tssp_descriptor_destroy(struct tssp_descriptor *descriptor)
{
    if (descriptor) {
        if (descriptor->destroy)
            descriptor->destroy(descriptor->decoded);
        free(descriptor->data);
        free(descriptor);
    }
}

void tssp_descriptors_destroy(struct tssp_descriptors *descriptors)
{
    struct tssp_descriptor *tmp;

    if (descriptors) {
        while ((tmp = STAILQ_FIRST(descriptors))) {
            STAILQ_REMOVE_HEAD(descriptors, entry);
            tssp_descriptor_destroy(tmp);
        }
    }
}

int tssp_descriptor_parse(struct tssp_descriptors *descriptors,
                          const uint8_t *start,
                          const uint8_t *end,
                          uint16_t info_length)
{
    int ret = 0;
    struct tssp_descriptor *desc;
    uint8_t tag;
    uint8_t length;

    TSSP_ASSERT(descriptors);
    TSSP_ASSERT(start);

    if (info_length > (end - start))
        return -1;

    end = start + info_length;
    while ((end - start) >= 2) {
        tag = start[0];
        length = start[1];
        start += 2;
        if ((end - start) >= length) {
            desc = tssp_descriptor_create(tag, length, start);
            if (desc == NULL)
                return -2;
            STAILQ_INSERT_TAIL(descriptors, desc, entry);
        }
        start += length;
        ret++;
    }

    return ret;
}

struct tssp *tssp_create(tssp_timer_create timer_create_func,
                         tssp_timer_delete timer_delete_func,
                         void *timer_userdata)
{
    struct tssp *parser;

    parser = malloc(sizeof(*parser));
    if (parser == NULL)
        return NULL;

    STAILQ_INIT(&parser->section_listeners);

    parser->timer_create = timer_create_func;
    parser->timer_delete = timer_delete_func;
    parser->timer_userdata = timer_userdata;

    if (tssp_impl_init(parser) != TSSP_STATUS_OK) {
        free(parser);
        return NULL;
    }

    return parser;
}

void tssp_destroy(struct tssp *parser)
{
    if (parser) {
        if (parser->impl.destroy)
            parser->impl.destroy(parser);
        free(parser);
    }
}

void tssp_push_packet(struct tssp *parser, const uint8_t *packet)
{
    TSSP_ASSERT(parser);
    TSSP_ASSERT(packet);
    TSSP_ASSERT(parser->impl.push_packet);

    parser->impl.push_packet(parser, packet);
}

struct tssp_section_listener *
tssp_add_section_filter(struct tssp *parser,
                        const struct tssp_section_filter *filter,
                        tssp_section_callback callback,
                        void *userdata)
{
    TSSP_ASSERT(parser);
    TSSP_ASSERT(filter);
    TSSP_ASSERT(callback);

    return parser->impl.add_section_filter(parser, filter, callback, userdata);
}

void tssp_remove_section_filter(struct tssp *parser,
                                struct tssp_section_listener *listener)
{
    TSSP_ASSERT(parser);
    TSSP_ASSERT(listener);

    parser->impl.remove_section_filter(parser, listener);
}

void tssp_section_filter_init(struct tssp_section_filter *filter,
                              uint16_t pid,
                              uint8_t table_id,
                              uint16_t table_id_extension,
                              uint16_t table_id_extension_mask,
                              uint8_t current_next_indicator,
                              uint32_t timeout)
{
    TSSP_ASSERT(filter);

    memset(filter->mask, 0, sizeof(filter->mask));
    memset(filter->value, 0, sizeof(filter->value));

    filter->pid = pid;

    filter->mask[0] = 0xFF;
    filter->value[0] = table_id;

    filter->mask[1] = (0xFF00 & table_id_extension_mask) >> 8;
    filter->mask[2] = (0x00FF & table_id_extension_mask);
    filter->value[1] = (0xFF00 & table_id_extension) >> 8;
    filter->value[2] = (0x00FF & table_id_extension);

    filter->mask[3] = 0x01;
    filter->value[3] = current_next_indicator ? 0x01 : 0x00;

    filter->timeout = timeout;
}

